package jmine.tec.security.permission;

import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import jmine.tec.utils.Tuple;
import jmine.tec.utils.reflection.AnnotationUtils;
import bancosys.tec.security.SecurityService;
import bancosys.tec.security.annotation.Secure;
import bancosys.tec.security.authorization.Permission;

/**
 * Factory para criar {@link PermissionChecker}
 * 
 * @author takeshi
 */
public class PermissionCheckerFactory {

    private final Map<Class<?>, PermissionChecker> classCheckers = new ConcurrentHashMap<Class<?>, PermissionChecker>();

    private final Map<Method, PermissionChecker> methodCheckers = new ConcurrentHashMap<Method, PermissionChecker>();

    private SecurityService securityService;

    /**
     * @return the securityService
     */
    public SecurityService getSecurityService() {
        return this.securityService;
    }

    /**
     * @param securityService the securityService to set
     */
    public void setSecurityService(SecurityService securityService) {
        this.securityService = securityService;
    }

    /**
     * Verifica se o usuario atual tem a permissao necessaria
     * 
     * @param owner Class
     * @return boolean
     */
    public final boolean currentUserHasPermission(Class<?> owner) {
        return this.getPermissionChecker(owner).hasPermission(this.getSecurityService().getCurrentThreadSubject());
    }

    /**
     * Verifica se o usuario atual tem a permissao necessaria
     * 
     * @param action Method
     * @return boolean
     */
    public final boolean currentUserHasPermission(Method action) {
        return this.getPermissionChecker(action).hasPermission(this.getSecurityService().getCurrentThreadSubject());
    }

    /**
     * Devolve um {@link PermissionChecker} para o metodo passada
     * 
     * @param action Method
     * @return {@link PermissionChecker}
     */
    public final PermissionChecker getPermissionChecker(Method action) {
        PermissionChecker checker = this.methodCheckers.get(action);
        if (checker != null) {
            return checker;
        }
        checker = this.createPermissionChecker(action);
        this.methodCheckers.put(action, checker);
        return checker;
    }

    /**
     * Devolve um {@link PermissionChecker} para a classe passada
     * 
     * @param class1 Class
     * @return {@link PermissionChecker}
     */
    public final PermissionChecker getPermissionChecker(Class<?> class1) {
        PermissionChecker checker = this.classCheckers.get(class1);
        if (checker != null) {
            return checker;
        }
        checker = this.createPermissionChecker(class1);
        this.classCheckers.put(class1, checker);
        return checker;
    }

    /**
     * Cria um novo {@link PermissionChecker} para a classe passada
     * 
     * @param class1 Class
     * @return {@link PermissionChecker}
     */
    private PermissionChecker createPermissionChecker(Class<?> class1) {
        Tuple<Class<?>, Secure> tuple = AnnotationUtils.findTypeAndAnnotation(class1, Secure.class);
        if (tuple == null) {
            return PermissionChecker.NO_CHECK;
        }
        Secure secureAnnotation = tuple.tail();
        if ("".equals(secureAnnotation.id()) || Permission.class.equals(secureAnnotation.annotationType())) {
            return PermissionChecker.LOGIN_REQUIRED;
        }
        final String permissionId = secureAnnotation.id();
        final Class<? extends Permission> type = secureAnnotation.permissionType();
        return new PermissionCheckerImpl(this.securityService, permissionId, type);
    }

    /**
     * Cria um novo {@link PermissionChecker} para o metodo passado
     * 
     * @param action Method
     * @return {@link PermissionChecker}
     */
    private PermissionChecker createPermissionChecker(Method action) {
        Secure secureAnnotation = action.getAnnotation(Secure.class);
        if (secureAnnotation == null) {
            return PermissionChecker.NO_CHECK;
        }
        if ("".equals(secureAnnotation.id()) || Permission.class.equals(secureAnnotation.annotationType())) {
            return PermissionChecker.LOGIN_REQUIRED;
        }
        final String permissionId = secureAnnotation.id();
        final Class<? extends Permission> type = secureAnnotation.permissionType();
        return new PermissionCheckerImpl(this.securityService, permissionId, type);
    }
}
